package stardust.product.platform.webc.spi.servlet;


import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import stardust.core.java.reflect.MethodReflect;
import stardust.core.java.reflect.TypeUtils;
import stardust.product.platform.webc.RenderHandle;
import stardust.product.platform.webc.WebcConfig;
import stardust.product.platform.webc.WebcContext;
import stardust.product.platform.webc.WebcException;
import stardust.product.platform.webc.WebcParameterConvert;
import stardust.product.platform.webc.WebcRequestHandler;
import stardust.product.platform.webc.WebcResourceReference;
import stardust.product.platform.webc.annotation.Parameter;
import stardust.product.platform.webc.spi.servlet.support.MultipartContentRequestSupport;
import stardust.product.platform.webc.spi.servlet.support.PageUtils;
import stardust.product.platform.webc.support.StaticUrlUtils;


public class ServletWebcRequestHandler implements WebcRequestHandler
{
  
  private MultipartContentRequestSupport multipartContentRequestSupport;

  private HttpServletRequest request;

  private HttpServletResponse response;
  
  private WebcContext context;

  public ServletWebcRequestHandler(WebcContext context, HttpServletRequest request, HttpServletResponse response)
  {
    this.context = context;
    this.request = request;
    this.response = response;
    
    try
    {
      this.multipartContentRequestSupport = new MultipartContentRequestSupport(this.request);
    }
    catch (FileUploadException e)
    {
      throw new WebcException(e);
    }
  }
  
  public WebcContext getWebcContext()
  {
    return this.context;
  }

  public RenderHandle getDefaultRenderer()
  {
    String defaultRenderer;
    
    defaultRenderer = this.context.getConfig().getParameter().get(WebcConfig.CFG_DEFAULT_WEB_RENDERER);
    
    if (defaultRenderer == null)
    {
      return new JspRenderer();
    }
    else
    {
      return this.context.getContainer().getRenderer(defaultRenderer);
    }
  }

  public Object process(String uri, WebcResourceReference reference)
  {
    Object[] parameterValueses;
    Object returnValue;

    try
    {
      parameterValueses = this.parseParameter(uri, reference);
    }
    catch(Exception ex)
    {
      throw new WebcException("无法访问资源[" + uri + " -> " + reference.getUri() + "]，执行参数转换时发生错误。", ex);
    }

    try
    {
      returnValue = MethodReflect.invoke(reference.getInstance(), reference.getTargetMethod(), parameterValueses);

      return returnValue;
    }
    catch(Exception ex)
    {
      throw new WebcException("无法访问资源[" + uri + " -> " + reference.getUri() + "]，调用资源处理程序时发生错误。", ex);
    }
  }
  
  public Map<String, Object> getUtils()
  {
    Map<String, Object> utils = new HashMap<String, Object>();
    
    utils.put("request", this.request);
    utils.put("response", this.response);
    utils.put("ctxpath", this.request.getContextPath());
    utils.put("utils", new PageUtils(this));
    
    return utils;
  }

  /**
   * 转换请求参数，从ServletRequest中获取实参并依照形参类型进行转换。 <br/>
   * 注意：仅支持基本数据类型、包装类型、日期类型、文件类型（HTTP上传）和一维数组的转换。
   * @throws IOException 
   * @throws ClassCastException 
   * @throws FileUploadException 
   * @throws IllegalAccessException 
   * @throws InstantiationException 
   */
  protected Object[] parseParameter(String uri, WebcResourceReference reference) throws ClassCastException, IOException, FileUploadException, InstantiationException, IllegalAccessException
  {
    Class<?>[] parameterClasses;
    Parameter[] parameterParams;
    Object[] parameterValueses;
    
    Map<String, String> restParameter;
    
    /*
     * 获取方法形参类型和形参标注，并为实参预留空间。
     */
    parameterClasses = reference.getTargetMethod().getParameterTypes();
    parameterValueses = new Object[parameterClasses.length];
    parameterParams = reference.getEntryParam();
    
    // 计算RESTful参数值。
    restParameter = StaticUrlUtils.getRestParameterMap(uri, reference.getUri());

    /**
     * 计算方法的实参，并依据形参类型进行转换。
     */
    MultipartContentRequestSupport r = this.multipartContentRequestSupport;
    
    for (int i = 0; i < parameterParams.length; i++)
    {
      if (parameterParams[i] != null)
      {
        if (parameterClasses[i].isArray())
        {
          /**
           * 处理数组参数。
           */
          if (parameterParams[i].converter() != WebcParameterConvert.class)
          {
            parameterValueses[i] = parameterParams[i].converter().newInstance().convert(this, reference, parameterParams[i], parameterClasses[i].getComponentType(), parameterClasses[i].isArray());
          }
          
          if (TypeUtils.testCastToType(parameterClasses[i].getComponentType(), InputStream.class))
          {
            FileItem[] items = r.getMultipartParameterValues(parameterParams[i].value());
            InputStream[] inputStreams = null;
            
            if (items != null)
            {
              inputStreams = new InputStream[items.length];
              
              for (int j = 0; j < inputStreams.length; j++)
              {
                inputStreams[j] = items[j].getInputStream();
              }
            }
            
            parameterValueses[i] = TypeUtils.caseArrayToType
            (
              inputStreams, parameterClasses[i], parameterParams[i].format()
            );
          }
          else if (TypeUtils.testCastToType(parameterClasses[i].getComponentType(), FileItem.class))
          {
            FileItem[] items = r.getMultipartParameterValues(parameterParams[i].value());
            
            parameterValueses[i] = TypeUtils.caseArrayToType
            (
                items, parameterClasses[i], parameterParams[i].format()
            );
          }
          else
          {
            /*
             * 解析REST风格的URI地址参数。
             */
            if (StaticUrlUtils.isRestParameter(parameterParams[i].value()))
            {
              parameterValueses[i] = new String[] {restParameter.get(parameterParams[i].value())};
            }
            
            parameterValueses[i] = TypeUtils.caseArrayToType
            (
              parameterValueses[i] == null ? r.getParameterValues(parameterParams[i].value()) : parameterValueses[i], parameterClasses[i], parameterParams[i].format()
            );
          }
          
          if ((parameterValueses[i] == null || ((Object[]) parameterValueses).length == 0) && parameterParams[i].required())
          {
            throw new WebcException("无法访问资源[" + uri + " -> " + reference.getUri() + "]，没有提供必须的参数[" + parameterParams[i].value() + "]。");
          }
        }
        else
        {
          /**
           * 处理单个参数。
           */
          if (parameterParams[i].converter() != WebcParameterConvert.class)
          {
            // 解析自定义转换器参数。
            parameterValueses[i] = parameterParams[i].converter().newInstance().convert(this, reference, parameterParams[i], parameterClasses[i], parameterClasses[i].isArray());
            
            if (parameterValueses[i] != null && ((Object[]) parameterValueses[i]).length > 0)
            {
              parameterValueses[i] = ((Object[]) parameterValueses[i])[0];
            }
          }
          
          if (TypeUtils.testCastToType(parameterClasses[i], InputStream.class))
          {
            FileItem item = r.getMultipartParameter(parameterParams[i].value());
            
            parameterValueses[i] = TypeUtils.castToType
            (
              item == null ? null : item.getInputStream(), parameterClasses[i], parameterParams[i].format()
            );
          }
          else if (TypeUtils.testCastToType(parameterClasses[i], FileItem.class))
          {
            FileItem item = r.getMultipartParameter(parameterParams[i].value());
            
            parameterValueses[i] = TypeUtils.castToType
            (
              item, parameterClasses[i], parameterParams[i].format()
            );
          }
          else
          {
            /*
             * 解析REST风格的URI地址参数。
             */
            if (StaticUrlUtils.isRestParameter(parameterParams[i].value()))
            {
              parameterValueses[i] = restParameter.get(parameterParams[i].value());
            }
            
            parameterValueses[i] = TypeUtils.castToType
            (
              parameterValueses[i] == null ? r.getParameter(parameterParams[i].value()) : parameterValueses[i], parameterClasses[i], parameterParams[i].format()
            );
          }
          
          if ((parameterValueses[i] == null || (parameterValueses[i] instanceof String ? ((String) parameterValueses[i]).isEmpty() : false)) && parameterParams[i].required())
          {
            throw new WebcException("无法访问资源[" + uri + " -> " + reference.getUri() + "]，没有提供必须的参数[" + parameterParams[i].value() + "]。");
          }
        }
      }
      else
      {
        /**
         * 处理特殊参数，此类参数并非由客户端传送过来，一般用于获取当前Webc上下文的一些信息。
         */
        if (TypeUtils.testCastToType(parameterClasses[i], WebcContext.class))
        {
          parameterValueses[i] = this.context;
        }
        else if (TypeUtils.testCastToType(parameterClasses[i], WebcRequestHandler.class))
        {
          parameterValueses[i] = this;
        }
        else if (TypeUtils.testCastToType(parameterClasses[i], ServletResponse.class))
        {
          parameterValueses[i] = this.getResponse();
        }
        else if (TypeUtils.testCastToType(parameterClasses[i], ServletRequest.class))
        {
          parameterValueses[i] = this.getRequest();
        }
        else
        {
          throw new WebcException("无法访问资源[" + uri + " -> " + reference.getUri() + "]，无法识别没有@Parameter标注并且不在支持列表以内的参数。");
        }
      }
    }

    return parameterValueses;
  }
  
  public MultipartContentRequestSupport getMultipartContentRequestSupport()
  {
    return this.multipartContentRequestSupport;
  }

  public HttpServletRequest getRequest()
  {
    return this.request;
  }

  public HttpServletResponse getResponse()
  {
    return this.response;
  }

}
